using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using minega.Properties;

namespace minega
{
	// Change name? Game, maybe?
	public class Grid
	{
		// Remove if you like, it makes it a bit easier to navigate the code..
		#region Vars...
		/// <summary>
		/// The mines.
		/// </summary>
		private Mines _mines;
		/// <summary>
		/// The states of the different cells.
		/// </summary>
		private CellStates _states;
		/// <summary>
		/// Width and height of the grid.
		/// </summary>
		private int _width, _height;
		/// <summary>
		/// Position in the grid (i.e position of upper-left corner of the screen)
		/// </summary>
		private int _posX, _posY;
		/// <summary>
		/// Number of visible cells in the x-direction and y-direction.
		/// </summary>
		private int _cellsXWays, _cellsYWays;
		/// <summary>
		/// Coordinates of the currently pressed cells.
		/// </summary>
		private List<Point> _pressedCells = new List<Point>();
		/// <summary>
		/// Flag indicating if game over.
		/// </summary>
		private bool _gameOver;
		/// <summary>
		/// List containing the different images of the cells
		/// </summary>
		private Image[] _imgList = new Image[16];
		/// <summary>
		/// The bitmap with the current portion of the grid drawn to it.
		/// </summary>
		private Bitmap _buffer;
		/// <summary>
		/// Gets the screen bitmap.
		/// </summary>
		public Bitmap Buffer
		{
			get { return _buffer; }
		}
        /// <summary>
        /// The actual width of the game, in whole cells
        /// </summary>
        public int Width
        {
            get { return _width; }
        }
        /// <summary>
        /// The actual height of the game, in whole cells
        /// </summary>
        public int Height
        {
            get { return _height; }
        }
		/// <summary>
		/// Gets the number of mines in the game.
		/// </summary>
		public int Mines
		{
			get { return _mines.Count; }
		}
        /// <summary>
        /// The visible width, in whole cells
        /// </summary>
        public int VisibleWidth
        {
            get { return _cellsXWays; }
        }
        /// <summary>
        /// The visible height, in whole cells
        /// </summary>
        public int VisibleHeight
        {
            get { return _cellsYWays; }
        }
        /// <summary>
        /// The current offset x, in whole cells
        /// </summary>
        public int OffsetX
        {
            get { return _posX; }
        }
        /// <summary>
        /// The current offset y, in whole cells
        /// </summary>
        public int OffsetY
        {
            get { return _posY; }
		}
		#endregion
		/// <summary>
		/// Initializes the grid to a standard size (30x60 with 99 mines).
		/// </summary>
		public Grid()
            : this(30, 16, 99) // Start-up as "BIGSWEEPER"
		{
		}
		/// <summary>
		/// Initializes the grid with the given size and amount of mines.
		/// </summary>
		/// <param name="w">Width of the grid.</param>
		/// <param name="h">Height of the grid.</param>
		/// <param name="m">Number of mines.</param>
		public Grid(int w, int h, int m)
		{
			_width = w;
			_height = h;
			_mines = new Mines(w, h, m);
			_mines.GenerateNew();
			_states = new CellStates(w, h);
			_states.Normal();

			_posX = 0;
			_posY = 0;
			_cellsXWays = 1;
			_cellsYWays = 1;

            _buffer = new Bitmap(_cellsXWays * 16, _cellsXWays * 16);
			LoadImages();
			UpdateBuffer();
		}
		/// <summary>
		/// Loads all the different images into the _imgList-array.
		/// </summary>
		private void LoadImages()
		{
			_imgList[CellStateFlags.Open0] = Resources.open0;
            _imgList[CellStateFlags.Open1] = Resources.open1;
            _imgList[CellStateFlags.Open2] = Resources.open2;
            _imgList[CellStateFlags.Open3] = Resources.open3;
            _imgList[CellStateFlags.Open4] = Resources.open4;
            _imgList[CellStateFlags.Open5] = Resources.open5;
            _imgList[CellStateFlags.Open6] = Resources.open6;
            _imgList[CellStateFlags.Open7] = Resources.open7;
            _imgList[CellStateFlags.Open8] = Resources.open8;
            _imgList[CellStateFlags.Normal] = Resources.normal;
            _imgList[CellStateFlags.Flag] = Resources.flag;
            _imgList[CellStateFlags.QMark] = Resources.questionMark;
			_imgList[CellStateFlags.OpenQMark] = Resources.openQuestionMark;
			_imgList[CellStateFlags.OpenMineCorrect] = Resources.openMineCorrect;
			_imgList[CellStateFlags.OpenMineClicked] = Resources.openMineClicked;
			_imgList[CellStateFlags.OpenMineIncorrect] = Resources.openMineIncorrect;
		}
		/// <summary>
		/// Updates the buffer with the visible cells.
		/// </summary>
		private void UpdateBuffer()
		{
			Graphics g = Graphics.FromImage(_buffer);
			for (int x = 0; x < _cellsXWays; x++)
			{
				for (int y = 0; y < _cellsYWays; y++)
				{
					g.DrawImage(_imgList[_states[x + _posX, y + _posY]], new Point(x * 16, y * 16));
				}
			}
		}
		/// <summary>
		/// Updates the cell at (x, y) (screen pos) in the buffer.
		/// </summary>
		/// <param name="x">x-coordinate of the cell.</param>
		/// <param name="y">y-coordinate of the cell.</param>
		private void UpdateBufferAt(int x, int y)
		{
			Graphics g = Graphics.FromImage(_buffer);
            g.DrawImage(_imgList[_states[x + _posX, y + _posY]], new Point(x * 16, y * 16));
		}
		/// <summary>
		/// Ends the game.
		/// </summary>
		/// <param name="cellX">x-coordinate of clicked mine.</param>
		/// <param name="cellY">y-coordinate of clicked mine.</param>
		private void GameOver(int cellX, int cellY)
		{
			for (int y = 0; y < _height; y++)
			{
				for (int x = 0; x < _width; x++)
				{
					if (_mines[x, y])
						_states[x, y] = CellStateFlags.OpenMineCorrect;
					else if (_states[x, y] == CellStateFlags.Flag)
						_states[x, y] = CellStateFlags.OpenMineIncorrect;
				}
			}
			_gameOver = true;
			_states[cellX, cellY] = CellStateFlags.OpenMineClicked;
			UpdateBuffer();
		}
		/// <summary>
		/// Initializes a new game with the "old" size and amount of mines.
		/// </summary>
		public void NewGame()
		{
			_mines.GenerateNew();
			_states.Normal();
			if (_width < _cellsXWays)
				_cellsXWays = _width;
			if (_height < _cellsYWays)
				_cellsYWays = _height;

			_gameOver = false;

			_buffer = new Bitmap(_cellsXWays * 16, _cellsYWays * 16);
			// Center the screen in the grid
			_posX = (_width - _cellsXWays) / 2;
			_posY = (_height - _cellsYWays) / 2;
			UpdateBuffer();
		}
		/// <summary>
		/// Initializes a new game with the given size and amount of mines.
		/// </summary>
		/// <param name="w">Width of the grid.</param>
		/// <param name="h">Height of the grid.</param>
		/// <param name="mines">Number of mines.</param>
		public void NewGame(int w, int h, int mines)
		{
			_width = w;
			_height = h;
			_mines = new Mines(w, h, mines);
			_states = new CellStates(w, h);
			NewGame();
		}
		/// <summary>
		/// Moves the screen the specified amount of steps in each direction.
		/// </summary>
		/// <param name="cX">Steps to move x-ways.</param>
		/// <param name="cY">Steps to move y-ways.</param>
		public void Move(int cX, int cY)
		{
			_posX = cX;
			_posY = cY;
			UpdateBuffer();
		}
		/// <summary>
		/// Resizes the buffer-image to the specified size.
		/// </summary>
		/// <param name="cellsX">Number of cells in x-direction.</param>
		/// <param name="cellsY">Number of cells in y-direction.</param>
		public void Resize(int cellsX, int cellsY)
		{
			if (cellsX == 0 || cellsY == 0)
				return;

            _cellsXWays = Math.Min(cellsX, _width);
            _cellsYWays = Math.Min(cellsY, _height);

            if (_posX + _cellsXWays > _width)
                _posX = _width - _cellsXWays;

            if (_posY + _cellsYWays > _height)
                _posY = _height - _cellsYWays;

			_buffer = new Bitmap(_cellsXWays * 16, _cellsYWays * 16);
			UpdateBuffer();
		}
        ///// <summary>
        ///// Opens all neighbours to the given cell.
        ///// </summary>
        ///// <param name="cellX">x-coordinate, relative grid, of cell to open.</param>
        ///// <param name="cellY">y-coordinate, relative grid, of cell to open.</param>
        //private void OpenNeighbours(int cellX, int cellY)
        //{
        //    // TODO: Place in thread and open all in screen first.
        //    if ((cellX > _width) || (cellY > _height))
        //        throw new IndexOutOfRangeException();

        //    Rectangle rect = new Rectangle(0, 0, _width, _height);
        //    Rectangle rectScreen = new Rectangle(_posX, _posY, _cellsXWays, _cellsYWays);
        //    Stack<UInt16> toProcess = new Stack<UInt16>();
        //    toProcess.Push((UInt16)cellX);
        //    toProcess.Push((UInt16)cellY);

        //    while (toProcess.Count > 0)
        //    {
        //        // TODO: Fix the stack-size problem
        //        // Size of stack grows too fast...
        //        UInt16 y = toProcess.Pop();
        //        UInt16 x = toProcess.Pop();
        //        Point pt = new Point(x, y);
        //        for (byte b = Neighbour.UL; b <= Neighbour.DR; b++)
        //        {
        //            Point nPt = NeighbourPos.GetNeighbour(b, pt.X, pt.Y);
        //            if (rect.Contains(nPt))
        //            {
        //                if (_states[nPt.X, nPt.Y] == CellStateFlags.Normal)
        //                {
        //                    _states[nPt.X, nPt.Y] = _mines.NeightbouringMines(nPt.X, nPt.Y);
        //                    if (rectScreen.Contains(nPt))
        //                        UpdateBufferAt(nPt.X - _posX, nPt.Y - _posY);
        //                    if (_states[nPt.X, nPt.Y] == CellStateFlags.Open0)
        //                    {
        //                        toProcess.Push((UInt16)nPt.X);
        //                        toProcess.Push((UInt16)nPt.Y);
        //                    }
        //                }
        //            }
        //        }
        //    }
        //}

		public void ToggleFlag(int cellX, int cellY)
		{
			if (_gameOver)
				return;

			cellX += _posX;
			cellY += _posY;

			if ((cellX > _width) || (cellY > _height))
				throw new IndexOutOfRangeException();

			if (_states[cellX, cellY] == CellStateFlags.Normal)
			{
				_states[cellX, cellY] = CellStateFlags.Flag;
				UpdateBufferAt(cellX - _posX, cellY - _posY);
			}
			else if (_states[cellX, cellY] == CellStateFlags.Flag)
			{
				_states[cellX, cellY] = CellStateFlags.QMark;
				UpdateBufferAt(cellX - _posX, cellY - _posY);
			}
			else if (_states[cellX, cellY] == CellStateFlags.QMark)
			{
				_states[cellX, cellY] = CellStateFlags.Normal;
				UpdateBufferAt(cellX - _posX, cellY - _posY);
			}
		}

		public void Press(int cellX, int cellY)
		{
			if (_gameOver)
				return;

			cellX += _posX;
			cellY += _posY;

			if ((cellX > _width) || (cellY > _height))
				throw new IndexOutOfRangeException();

			if (_pressedCells.Count == 1)
			{
				if (_pressedCells[0].X != cellX || _pressedCells[0].Y != cellY)
					UnPress();
			}
			else
				_pressedCells.Clear();

			if (_states[cellX, cellY] == CellStateFlags.Normal)
			{
				_pressedCells.Add(new Point(cellX, cellY));
				_states[cellX, cellY] = CellStateFlags.Open0;
				UpdateBufferAt(cellX - _posX, cellY - _posY);
			}
            else if (_states[cellX, cellY] == CellStateFlags.QMark)
            {
                _pressedCells.Add(new Point(cellX, cellY));
                _states[cellX, cellY] = CellStateFlags.OpenQMark;
                UpdateBufferAt(cellX - _posX, cellY - _posY);
            }
		}

		public void PressNeighbours(int cellX, int cellY)
		{
			if (_gameOver)
				return;

			cellX += _posX;
			cellY += _posY;

			if ((cellX > _width) || (cellY > _height))
				throw new IndexOutOfRangeException();

			if (_pressedCells.Count > 0)
			{
				if (_pressedCells[0].X != cellX || _pressedCells[0].Y != cellY)
					UnPress();
			}
			// First store the pressed cell, so we can easily identify which cells neghbours was pressed
			_pressedCells.Add(new Point(cellX, cellY));
			// No need to press if outside screen
			Rectangle rectScreen = new Rectangle(_posX, _posY, _cellsXWays, _cellsYWays);
			for (byte b = Neighbour.UL; b <= Neighbour.DR; b++)
			{
				Point pt = NeighbourPos.GetNeighbour(b, cellX, cellY);
				if (rectScreen.Contains(pt))
				{
					if (_states[pt.X, pt.Y] == CellStateFlags.Normal)
					{
						_states[pt.X, pt.Y] = CellStateFlags.Open0;
                        _pressedCells.Add(pt);
                        UpdateBufferAt(pt.X - _posX, pt.Y - _posY);
                    }
                    else if (_states[pt.X, pt.Y] == CellStateFlags.QMark)
                    {
                        _states[pt.X, pt.Y] = CellStateFlags.Open0;
                        _pressedCells.Add(pt);
                        UpdateBufferAt(pt.X - _posX, pt.Y - _posY);
                    }
				}
			}
		}

		public void UnPress()
		{
			Rectangle rect = new Rectangle(0, 0, _width, _height);
			if (_pressedCells.Count > 1)
				_pressedCells.RemoveAt(0);
			for (int i = 0; i < _pressedCells.Count; i++)
			{
				if (rect.Contains(_pressedCells[i]))
				{
                    if (_states[_pressedCells[i].X, _pressedCells[i].Y] == CellStateFlags.Open0)
                    {
                        _states[_pressedCells[i].X, _pressedCells[i].Y] = CellStateFlags.Normal;
                        UpdateBufferAt(_pressedCells[i].X - _posX, _pressedCells[i].Y - _posY);
                    }
                    else if (_states[_pressedCells[i].X, _pressedCells[i].Y] == CellStateFlags.OpenQMark)
                    {
                        _states[_pressedCells[i].X, _pressedCells[i].Y] = CellStateFlags.QMark;
                        UpdateBufferAt(_pressedCells[i].X - _posX, _pressedCells[i].Y - _posY);
                    }
				}
			}
			_pressedCells.Clear();
		}

        public void Click(int cellX, int cellY)
        {
			if (_gameOver)
				return;

            cellX += _posX;
            cellY += _posY;

            if ((cellX > _width) || (cellY > _height))
                throw new IndexOutOfRangeException();

            if (_states[cellX, cellY] == CellStateFlags.Normal)
            {
				if (_mines[cellX, cellY])
					GameOver(cellX, cellY);
				else
				{
					_states[cellX, cellY] = _mines.NeightbouringMines(cellX, cellY);
					// Open neighbours
					if (_states[cellX, cellY] == CellStateFlags.Open0)
					{
                        new NeighbourSolver(_mines, _states).Solve(Convert.ToUInt32(cellX), Convert.ToUInt32(cellY));
                        UpdateBuffer();
					}
				}
                UpdateBufferAt(cellX - _posX, cellY - _posY);
            }
        }

		public void ClickNeighbours(int cellX, int cellY)
		{
			if (_gameOver)
				return;

			cellX += _posX;
			cellY += _posY;

			if ((cellX > _width) || (cellY > _height))
				throw new IndexOutOfRangeException();

			if (_states[cellX, cellY] >= CellStateFlags.Open1 && _states[cellX, cellY] <= CellStateFlags.Open8)
			{
				// Do not open if the amount of flags and mines doesn't match
				if (_mines.NeightbouringMines(cellX, cellY) != _states.NeighbouringFlags(cellX, cellY))
					return;

				Rectangle rect = new Rectangle(0, 0, _width, _height);
				for (byte b = Neighbour.UL; b <= Neighbour.DR; b++)
				{
					Point pt = NeighbourPos.GetNeighbour(b, cellX, cellY);
					if (rect.Contains(pt))
					{
						if (_states[pt.X, pt.Y] == CellStateFlags.Normal)
						{
							if (_mines[pt.X, pt.Y])
								GameOver(pt.X, pt.Y);
							else
							{
								_states[pt.X, pt.Y] = _mines.NeightbouringMines(pt.X, pt.Y);
								// Open neighbours
								if (_states[pt.X, pt.Y] == CellStateFlags.Open0)
								{
                                    // TODO: Fix this odd bug.
                                    // Sometimes the solver doesn't open all neighbours to the specific cell...
                                    new NeighbourSolver(_mines, _states).Solve(Convert.ToUInt32(cellX), Convert.ToUInt32(cellY));
                                    UpdateBuffer();
								}
							}
							UpdateBufferAt(pt.X - _posX, pt.Y - _posY);
						}
					}
				}
			}
		}
	}
}